﻿#region File Description
/*-----------------------------------------------------------------------------
 * Class: Employee
 *
 * Employee is a DrawableGameComponent and a base class for ...
 * All employees have needs... can be instructed...
 *
 *
 * Notes: Employee will be a base class for specific employees, if we get there.
 -------------------------------------------------------------------------------*/
#endregion

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Cubezicles
{
    public class Employee : ClickableGameComponent
    {
        // Employee faces at this angle in the beginning
        private static readonly Quaternion startOrientation =
            Quaternion.CreateFromAxisAngle(Vector3.Up, MathHelper.PiOver4 * 3);

        // Distance from the origin to the employee's workstation
        //public static readonly Vector3 CubeOffset = new Vector3(5, 0, -5);

        // Employee's home cubicle
        // temp setting this to public, because when fire need to get rid of employee AND their cubicle....
        // ....need to think of a better way to do this
        public Cubicle cubicle;     

        
        // can/should maybe condense these?
        public MoodLevels MoodLevels { get { return needsManager.moodLevels; } }

        public NeedsManager.State CurrentState
        {
            get { return needsManager.currentState; }
            set { needsManager.currentState = CurrentState; }
        }

        // there's probs a better way to do this... but its friggin LIGHT outside right now so... WHATEVER
        // outfit to draw in avatar window thingy
        public Texture2D body;

        Texture2D currentEmo; // thought bubbles
        public enum Emotion { None = 5, Hungry = 0, Bored = 1, Sleepy = 2, Unfocused = 3, HaveToGo = 4 }
        public Emotion currentEmotion = Emotion.None;

        protected Texture2D faceEcstatic;
        protected Texture2D faceHappy;
        protected Texture2D faceMeh;
        protected Texture2D faceSad;
        protected Texture2D faceDespair;

        private String baseFaceFilePath = "";

        #region employeeFileNames

        //internal const string devBodyFilePath = "Models/Employees/Dev/body_front2";

        internal const string devFacesFilePath = "Models/Employees/Dev/";//"Models/Employees/Dev/Faces/";
        internal const string leadDevFacesFilePath = "Models/Employees/LeadDev/"; //"Models/Employees/LeadDev/Faces/";
        internal const string slackerFacesFilePath = "Models/Employees/Slacker/"; //Models/Employees/Slacker/Faces/";
        internal const string steveFacesFilePath = "Models/Employees/Steve/"; //Models/Employees/Steve/Faces/";
        internal const string uxFacesFilePath = "Models/Employees/UXDesigner/";//"Models/Employees/UXDesigner/Faces";
        #endregion


        
        
        // Reflects whther the state is recently changeds..... not sure about use of this  
        public bool changeInState = false;
        public bool isMoving = false;


        //--- Public getters/setters for member variables ---//

        // The overall mood (a calculation of all of their current need levels)
        public float OverallMood { get; private set; }

        public string Name { get; private set; } // the "friendly name" of this employee

        // The employee ID of this employee (used by EmployeeManager)
        public int ID { get; set; }

        // Return whether this employee is the currently selected one
        // the currently selected employee is handled by the game object
        public new Boolean IsSelected { get { return Game1.CurrentEmployee == this; } }


        // -------------- in progress -----------

        // The quality of work that this employee produces
        // used for a revenue multiplier possibly? in progress.....................
        public int QualityOfWork { get; set; }
        
        // in progress......................................
        public int AttentionSpan { get; set; }

        public int Salary { get; protected set; }

        


        private TimeSpan dailyTimeSpentWorking = new TimeSpan(0), timeWorkedYesterday;

        // Pathfinder nonsense
        public Node SrcNode, DestNode, HomeNode;
        public List<Node> path = null;

       
        

        private Game1 game1 { get { return (Game1)Game; } }

        public NeedsManager needsManager; // public just so we can toggle on/off using the W key

        public Employee(Game1 game, String modelFileName) : base(game, new Vector3(0, 0, 0), modelFileName)
        {
            // NAUGHTY HACKY ALTERNATIVE CONSTRUCTOR
        }

        /// <summary>
        /// Employee class constructor.
        /// </summary>
        public Employee(Game1 game, String name, int id, Cubicle cube, String modelFileName, String baseFacePath)
            : base(game, cube.Position /*+ CubeOffset*/, modelFileName)
        {
            this.DrawOrder = (int)Game1.DisplayOrder.Objects;
            // Save these parameters for later
            cubicle = cube;
            Name = name;
            ID = id;
            baseFaceFilePath = baseFacePath;

            // is this resetting from the subclasses***???
            QualityOfWork = 1; // default 0, no additional add to revenue<--Shouldn't this be 1 for a multiplier?

            Orientation = startOrientation;
            Scaling = Vector3.One * 2;

          
               HomeNode = game.gra.nodes[game.graphi + id]; // argument out of range exception unhandled***
               SrcNode = HomeNode;
               DestNode = HomeNode;

            // Manages all of the employee's needs and decay rates and such
            needsManager = new NeedsManager(this, game);
            needsManager.nextState = NeedsManager.State.Working;
            
            // Set all Need levels to 100% initially, and time worked in day so far to 0
            ResetEmployee();


            
        }

        /// <summary>
        /// Start of a new day - Set all employee Need levels to 100%/full,
        /// and set time worked in day so far to 0, and state = working
        /// </summary>
        public void ResetEmployee()
        {
            needsManager.moodLevels = new MoodLevels()
            {
                hunger = 1f,
                fun = 1f,
                energy = 1f,
                bladder = 1f,
                focus = 1f
            };

            // also reset daily time spent working to 0 at start of new day
            dailyTimeSpentWorking = new TimeSpan(0);

            // todo: set position back in cubicle 

            needsManager.currentState = NeedsManager.State.Working;
            needsManager.nextState = NeedsManager.State.Working;
        }


        /// <summary>
        /// Load content for the Employee
        /// </summary>
        protected override void LoadContent()
        {
            // whether they are in need of something/low on a need/need they are lowest on
            currentEmo = Game.Content.Load<Texture2D>("Textures/emotions"); // current emotion thought bubble
            body = Game.Content.Load<Texture2D>(baseFaceFilePath + "body_front2");

            baseFaceFilePath += "Faces/";

            faceEcstatic = Game.Content.Load<Texture2D>(baseFaceFilePath + "ecstatic");
            faceHappy = Game.Content.Load<Texture2D>(baseFaceFilePath + "happy");
            faceMeh = Game.Content.Load<Texture2D>(baseFaceFilePath + "meh");
            faceSad = Game.Content.Load<Texture2D>(baseFaceFilePath + "sad");
            faceDespair = Game.Content.Load<Texture2D>(baseFaceFilePath + "despair");

            base.LoadContent();
        }

        public override void Draw(GameTime gameTime)
        {
            // If the employee is "at home", visibly indicate to player by drawing them orange-ish
            if (CurrentState == NeedsManager.State.AtHome)
            {
                PGCModel.ForEachEffect(ef => { ef.EmissiveColor = new Vector3(1.0f, 0.4f, 0f); });
            }
            else
            {
                PGCModel.ForEachEffect(ef => { ef.EmissiveColor = Vector3.Zero; });
            }

            base.Draw(gameTime);
        }

        public void writeStats(SpriteBatch spr)
        {
            needsManager.WriteStats(spr);
        }

        /// <summary>
        /// Called when the GameComponent needs to be updated. Override this method with component-specific update code.
        /// </summary>
        /// <param name="gameTime">Time elapsed since the last call to Update</param>
        public override void Update(GameTime gameTime)
        {

            //--- Different decay rates based on current State of employee ---//

            // Get the decay rates based on the current State of employee
            // if we had more objects, this would probably go through an object... ***** Steve todo
            // ...but we only have one object per need, and one state per need, plus a couple
            
            needsManager.UpdateEmployeeState();
            needsManager.UpdateDecayRates();

            // Update the current need levels of the employee with the current decay rates
            needsManager.UpdateNeedLevels();
            moveEmployee();

            if(CurrentState == NeedsManager.State.Working)
            {
                dailyTimeSpentWorking = dailyTimeSpentWorking.Add(
                    new TimeSpan(gameTime.ElapsedGameTime.Ticks * (int)Game1.time.CurrTimeSpeed));
            }

            // Update the overall mood (used for drawing background color
            // + possibly facial expression of avatar profile image)... getFace()
            CalculateOverallMood();

            UpdateEmotion();

            base.Update(gameTime);
        }

        #region PathFinding
        public void writeGraph(SpriteBatch spr)
        {
            string[] writeus = {"Source Node: " + SrcNode.ToString(), 
                                   "Dest Node: " + DestNode.ToString(), 
                                   "Home Node: " + HomeNode.ToString()};
            float y = 200f;
            foreach (string writeme in writeus)
            {
                spr.DrawString(game1.Fonts["status"], writeme, new Vector2(50, y), Color.Red);
                y += game1.Fonts["status"].MeasureString(writeme).Y;
            }
            if (path != null)
            {
                spr.DrawString(game1.Fonts["status"], "Path is not null", new Vector2(50, y), Color.Red);
                y += game1.Fonts["status"].MeasureString("Path is not null").Y;

                foreach (Node n in path)
                {
                    spr.DrawString(game1.Fonts["status"], "Path:" + n.ToString(), new Vector2(50, y), Color.Red);
                    y += game1.Fonts["status"].MeasureString(n.ToString()).Y;
                }
            }

        }

        /// <summary>
        /// Set the destination node of the employee.
        /// </summary>
        /// <returns></returns>
        public void setDestination(Node dest)
        {
            DestNode = dest;
            path = null;
            isMoving = true;
        }

        // check if they reached their destination, also indicate to the object htat the user arrived (to trigger sounds)
        public bool ReachedDestination()
        {
            if(SrcNode == DestNode)
            {
                /*ItemManager im = game1.ItemManager;
                if (DestNode == im.nvend) im.vend.UserArrived();
                else if (DestNode == im.ndrink) im.drink.UserArrived();
                else if (DestNode == im.npin) im.pinball.UserArrived();
                else if (DestNode == im.nbath) im.bath.UserArrived();*/

                return true;
            }

            return false;
        }

        private void moveEmployee()
        {
            if (SrcNode != DestNode)
            {
                if (path != null)
                {
                    if (Edge.closeEnough(new Node(Position), DestNode))
                    {
                        SrcNode = DestNode;
                        path.RemoveAt(0);
                        if (path.Count > 0)
                        {
                            DestNode = path[0];
                        }
                    }

                    Vector3 unitPos = Vector3.Normalize(DestNode.pos - Position);
                    Position += unitPos * calculateStep();
                }
                else
                {
                    path = Game1.gra.getPath(SrcNode, DestNode);
                    path.RemoveAt(0);
                    if (path != null)
                    {
                        DestNode = path[0];
                    }
                }
            }
            else
            {
                isMoving = false;
            }
        }

        private float calculateStep()
        {
            //Length * direction * speed
            return (float)Game1.time.CurrTimeSpeed / (10 * (float)Clock.TimeSpeed.SpeedPlay);
        }
        #endregion

        /// <summary>
        /// Calculate the overall mood of this employee based on their current need status levels.
        /// </summary>
        private void CalculateOverallMood()
        {
            // Just do a simple average of need levels to calculate overall mood
            OverallMood = (MoodLevels.hunger + MoodLevels.fun + MoodLevels.energy + MoodLevels.bladder + MoodLevels.focus) / 5f;
        }

        /// <summary>
        /// Update the thought bubble (emotion) above an employees head. Shows their most pertinent need.
        /// </summary>
        private void UpdateEmotion()
        {
            // limit where a little bubble shows over head
            float emotionLimit = .33f;

            // Emotion refers to the thought bubble moodlet thingy

            // Order of urgency to show: bladder, hunger, fun, energy, focus
            if (MoodLevels.bladder <= emotionLimit)
                currentEmotion = Emotion.HaveToGo;
            else if (MoodLevels.hunger <= emotionLimit)
                currentEmotion = Emotion.Hungry;
            else if (MoodLevels.fun <= emotionLimit)
                currentEmotion = Emotion.Bored;
            else if (MoodLevels.energy <= emotionLimit)
                currentEmotion = Emotion.Sleepy;
            else if (MoodLevels.focus <= emotionLimit)
                currentEmotion = Emotion.Unfocused;
            else
                currentEmotion = Emotion.None;
        }

        /// <summary>
        /// at end of day, used to report how many hours this particular employee spent working
        /// used to generate revenue for day
        /// </summary>
        /// <returns></returns>
        public float GetDailyTimeSpentWorking()
        {
            if (!Game1.MoneyManager.reported)
                timeWorkedYesterday = dailyTimeSpentWorking;
            // This just returns hours. May want to adjust to return something idk, more comprehensive ***
            return timeWorkedYesterday.Hours + (timeWorkedYesterday.Minutes/60f);
        }

        /// <summary>
        /// Use the OverallMood to determine which face to draw on the avatar
        /// </summary>
        /// <returns>the face/facial expression to draw for the employee
        /// in their needsdisplay and employeemanager</returns>
        public Texture2D GetFace()
        {
            // Idk, maybe we want to process the face some more first.
            // I'll leave this as a function for now. 

            if (OverallMood >= .80) // esctatic
                return faceEcstatic;
            if (OverallMood >= .60) //happy
                return faceHappy;
            if (OverallMood >= .40) // meh
                return faceMeh;
            if (OverallMood >= .20) // sad
                return faceSad;
            
            return faceDespair; // despair
        }

        public Texture2D GetBody()
        {
            return body;
        }

        


        #region WrapperMethodsforNeedManager


        /// <summary>
        /// Convert the current state to a string
        /// </summary>
        /// <returns>the employee's current state in printable string form</returns>
        public string GetStateString()
        {
            return needsManager.GetStateString();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public bool GetBackToWork()
        {
            return needsManager.GetBackToWork(); 
        }

        /// <summary>
        /// 
        /// </summary>
        public void GoHome() { needsManager.GoHome(); }


        #endregion

        // This is called from the EmployeeManagerPane class (even when pane not visible)
        // so that it is in the same drawing order as other UI
        public void DrawThoughtBubble()
        {
            // Don't draw a thought bubble if they are at home
            // The employee will have a colored aura instead (pink now, maybe orange later)
            if (CurrentState == NeedsManager.State.AtHome || currentEmotion == Emotion.None)
                return;

            // emotion = whether they are in need of something/low on a need/need they are lowest on
            //          ---> display thought bubble


            const int scale = 2; // sets the 1/scale

            Rectangle sourceRect;
            
            int destW = currentEmo.Width / (5 * scale);
            int destH = currentEmo.Height/scale;

            int spriteWidth = currentEmo.Width / 5;
            int spriteHeight = currentEmo.Height;

            // reference: public enum Emotion { None = 5, Hungry = 0, Bored = 1, Sleepy = 2, Unfocused = 3, HaveToGo = 4 }
            int x = (int)currentEmotion * spriteWidth;
            int y = 0;
            
            int xOffset = 50;  // offset for drawing thought bubble centered over employee
            int yOffset = 200; // distance above employee's head to display.... todo: adjust with zoom/panning

            sourceRect = new Rectangle(x, y, spriteWidth, spriteHeight);

            Game1.SpriteBatch.Begin();

            
            // todo: get reference for picking code
            // -----picking sample game from msdn rotating table-----........... -----//
            
            // Use Viewport.Project to project a world space point into screen space.
            // Project the vector (0,0,0) using the model's world matrix, and the view and projection matrices.
            // that will tell us where the model's origin is on the screen.
            // Need to do this because spritebatch.Draw takes in screen space coordinates,
            // but our employee/model's position is stored in world space
            Vector3 screenSpace = Game1.GraphicsDevice.Viewport.Project(
                                    Vector3.Zero,
                                    Game1.CurrentCam.ProjectionMatrix,
                                    Game1.CurrentCam.ViewMatrix,
                                    this.ModelMatrix);


            // we want to draw the text a little bit above that, so we'll use
            // the screen space position - 60 to move up a little bit. A better
            // approach would be to calculate where the top of the model is, and
            // draw there, but this is easier.
            
            int bubblePosX = (int) screenSpace.X - xOffset;
            int bubblePosY = (int) screenSpace.Y - yOffset;
            
            // Where to draw the thought bubble - destination rectangle
            Rectangle bubblePos = new Rectangle(bubblePosX, bubblePosY, destW, destH);


            Game1.SpriteBatch.Draw(currentEmo,
                bubblePos,  // destination rectangle
                sourceRect, // source rectangle
                Color.White);

            Game1.SpriteBatch.End();      
        }
    }
}
